from django import template
from django.template import Library, Node, TemplateSyntaxError, Variable, resolve_variable
from django.db.models import get_model
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _, ugettext as __
from django.utils.encoding import smart_unicode, smart_str, force_unicode
from django.utils.safestring import mark_safe, EscapeUnicode, mark_for_escaping
from django.core.urlresolvers import reverse, resolve
from tagging.models import Tag, TaggedItem
from tagging.utils import LINEAR, LOGARITHMIC
import re
import datetime
from BeautifulSoup import BeautifulSoup, Comment
from django.utils import text

from django.contrib.auth.models import User
from common import funcs

#from demoprofile.models import Profile
Profile = models.get_model("demoprofile","profile")
import random

from django.contrib.contenttypes.models import ContentType

register = template.Library()

class ChangeUrlArgs(template.Node):
    def __init__(self, arg_dict):
        self.args = arg_dict

    def render(self, context):
        request = context.get('request')
        self.url = request.path_info
        self.args =  self.args[1:-1]
        arg_dict = {}
        for line in self.args.split(','):
            line.strip()
            key, value = line.split(':')
            key = key.strip()
            value = value.strip()
            if key[0] in ['"',"'"]:
                key = eval(key)
            else:
                key = context[key]
            if value[0] in ['"',"'"]:
                value = eval(value)
            else:
                value = context[value]
            arg_dict.update({key: value})
        view, args, kwargs = resolve(self.url)
        kwargs.update(arg_dict)
        clean_url = reverse(view, args=args, kwargs=kwargs)
        if request.GET:
            zurl = clean_url + '?' + "&".join(['%s=%s' % (key, val) for key, val in request.GET.items()])
            return zurl
        else:
            return clean_url

class GetImageForm(template.Node):
    def __init__(self, var_name):
        self.var_name = var_name

    def render(self, context):
        form = ImageForm()
        context[self.var_name] = form
        return ""

class PrintMessage(template.Node):
    def __init__(self, var_name):
        self.var_name = var_name

    def render(self, context):
        message = context[self.var_name]
        t = message.split('|')
        if (t.__len__() > 1):
            return "<li class='{0}'>{1}</li>".format(t[0],__(t[1]))
        else:
            return "<li class='ok'>%s</li>" % smart_unicode(message)
        

@register.tag
def change_url_args(parser, token):
    """
    Return url with changed argument

    Syntax::

        {% change_url_args {'key':'value', key:value }%}

    Example usage::
        
        {% change_url_args {"order":'score'} %}
        if url is blogs/<order>/category/<slug>/
        and request.path_info was /blogs/publish/category/politics/
        this will generate url /blogs/score/category/politics/
        
    """
    try:
        tag_name, arg = token.contents.split(None, 1)
    except ValueError:
        raise template.TemplateSyntaxError, "%s tag requires arguments" % token.contents.split()[0]
    m = re.search(r'{([^{}]+)}', arg)
    if not m:
        raise template.TemplateSyntaxError, "%s tag had invalid arguments" % tag_name
    arg_dict = '{%s}' % m.groups()[0]
    return ChangeUrlArgs(arg_dict)

@register.tag
def get_image_form(parser, token):
    """
    Return url with changed argument

    Syntax::

        {% get_image_form imform %}

    Example usage::
        
        {% change_url_args imform %}
        {{ imform.as_table }}
        
    """
    try:
        tag_name, arg = token.contents.split(None, 1)
    except ValueError:
        raise template.TemplateSyntaxError, "%s tag requires arguments" % token.contents.split()[0]
    m = re.search(r'([a-zA-Z]+)', arg)
    if not m:
        raise template.TemplateSyntaxError, "%s tag had invalid arguments" % tag_name
    var_name = m.groups()[0]
    return GetImageForm(var_name)

@register.tag
def print_message(parser, token):
    """
    Return url with changed argument

    Syntax::

        {% print_message varname %}

    Example usage::
        
        message = 'error|operation unsuccessful'
        {% print_message message %} will return
        <li class='error'>operation unsuccessful</li>
        
    """
    try:
        tag_name, arg = token.contents.split(None, 1)
    except ValueError:
        raise template.TemplateSyntaxError, "%s tag requires arguments" % token.contents.split()[0]
    m = re.search(r'([a-zA-Z]+)', arg)
    if not m:
        raise template.TemplateSyntaxError, "%s tag had invalid arguments" % tag_name
    var_name = m.groups()[0]
    return PrintMessage(var_name)

@register.filter
def glow_message(msg, type=None):
    t = msg.split("|")
    if len(t) > 1:
        if type is None: type = t[0]
        msg = t[1]
    if type is None: type = 'accept'
    d = {'base': settings.MEDIA_URL, 'type':type, 'msg':msg}
    str = "<img class=\"fl\" style=\"margin:4px;\" src=\"%(base)simages/v2/%(type)s.png\"/> %(msg)s" % d
    return mark_safe(str)  
    

@register.filter("change_url_args")
def change_url_args_filter(value, args=''):
    """
        {{url|change_url_args:"{'slug': 'asdsd', 'id': '12'}"}}
    """
    if args=='':
        raise template.TemplateSyntaxError, "filter must be with arguments in dict style"
    
    arg_dict = eval(args)
    view, args, kwargs = resolve(value)
    kwargs.update(arg_dict)
    #print arg_dict
    return reverse(view, args=args, kwargs=kwargs)

@register.filter
def is_new(value, args=''):
    """
        {{date|is_new}}
        returns 'new_obj' if the date is less than 24 hours ago
        else ''
    """
    now = datetime.datetime.now()
    t = now - value
    if t.days == 0:
        return 'new_obj'
    return ''

@register.filter
def profile_url(user, args=''):
    """
        {{date|profile_url}}
        returns url to the profile page of the user
    """
    try:
        return Profile.objects.get(user=user).get_absolute_url()
    except:
        return ''
    
@register.filter
def coder_rating(user, args=''):
    """
        {{date|coder_rating}}
        returns coder rating
    """
    try:
        t = Profile.objects.get(user=user).coder_rating
        return '%.3f' % t
    except:
        return '0.000'

@register.filter
def author_rating(user, args=''):
    """
        {{date|user_raing}}
        returns author rating
    """
    try:
        t = Profile.objects.get(user=user).author_rating
        return '%.3f' % t
    except:
        return '0.000'

@register.filter
def by_tag(url_name, tag_name):
    """
        {{url_name|by_tag:"tag_name"}}
        returns reverse('url_name',kwargs={'tag':tag_name})
    """
    try:
        return reverse(url_name, kwargs={'tag':tag_name})
    except:
        return ''

@register.filter
def var_trans(str, var):
    p1 = str.index("%(")
    p2 = str.index(")s")
    key = str[p1+2:p2]
    return __(str % {key:var})

author_fields = {
 'ask.question': 'user',
 'ejudgemanager.analysis': 'author',
 'blog.post': 'author'
}

class TagCloudForModelNode(Node):
    def __init__(self, model, user_var, context_var, **kwargs):
        self.model = model
        self.user_var = user_var
        self.context_var = context_var
        self.kwargs = kwargs

    def render(self, context):
        model = get_model(*self.model.split('.'))
        if model is None:
            raise TemplateSyntaxError(_('tag_cloud_for_model tag was given an invalid model: %s') % self.model)
        t = self.user_var.split(".")
        if len(t) > 1:
            user = context[t[0]]
            del t[0]
            rest = ".".join(t)
            user = eval('user.%s' % rest)
        else:
            user = context[t[0]]
        self.kwargs.update({'filters': {'%s__username' %author_fields[self.model.lower()]:user.username}})
        context[self.context_var] = \
            Tag.objects.cloud_for_model(model, **self.kwargs)
        return ''

@register.tag
def tag_cloud_for_user(parser, token):
    """
    Retrieves a list of ``Tag`` objects for a given model, with tag
    cloud attributes set, and stores them in a context variable.

    Usage::

       {% tag_cloud_for_model [model] [user] as [varname] %}

    The model is specified in ``[appname].[modelname]`` format.

    Extended usage::

       {% tag_cloud_for_model [model] as [varname] with [options] %}

    Extra options can be provided after an optional ``with`` argument,
    with each option being specified in ``[name]=[value]`` format. Valid
    extra options are:

       ``steps``
          Integer. Defines the range of font sizes.

       ``min_count``
          Integer. Defines the minimum number of times a tag must have
          been used to appear in the cloud.

       ``distribution``
          One of ``linear`` or ``log``. Defines the font-size
          distribution algorithm to use when generating the tag cloud.

    Examples::

       {% tag_cloud_for_model products.Widget user as widget_tags %}
       {% tag_cloud_for_model products.Widget user as widget_tags with steps=9 min_count=3 distribution=log %}

    """
    bits = token.contents.split()
    len_bits = len(bits)
    if len_bits != 5 and len_bits not in range(7, 10):
        raise TemplateSyntaxError(_('%s tag requires either three or between five and seven arguments') % bits[0])
    if bits[3] != 'as':
        raise TemplateSyntaxError(_("second argument to %s tag must be 'as'") % bits[0])
    kwargs = {}
    if len_bits > 5:
        if bits[5] != 'with':
            raise TemplateSyntaxError(_("if given, fourth argument to %s tag must be 'with'") % bits[0])
        for i in range(6, len_bits):
            try:
                name, value = bits[i].split('=')
                if name == 'steps' or name == 'min_count':
                    try:
                        kwargs[str(name)] = int(value)
                    except ValueError:
                        raise TemplateSyntaxError(_("%(tag)s tag's '%(option)s' option was not a valid integer: '%(value)s'") % {
                            'tag': bits[0],
                            'option': name,
                            'value': value,
                        })
                elif name == 'distribution':
                    if value in ['linear', 'log']:
                        kwargs[str(name)] = {'linear': LINEAR, 'log': LOGARITHMIC}[value]
                    else:
                        raise TemplateSyntaxError(_("%(tag)s tag's '%(option)s' option was not a valid choice: '%(value)s'") % {
                            'tag': bits[0],
                            'option': name,
                            'value': value,
                        })
                else:
                    raise TemplateSyntaxError(_("%(tag)s tag was given an invalid option: '%(option)s'") % {
                        'tag': bits[0],
                        'option': name,
                    })
            except ValueError:
                raise TemplateSyntaxError(_("%(tag)s tag was given a badly formatted option: '%(option)s'") % {
                    'tag': bits[0],
                    'option': bits[i],
                })
    return TagCloudForModelNode(bits[1], bits[2], bits[4], **kwargs)

@register.filter
def texify(text, o=None):
    if o is None:
        url = 'latex/tmp.tmp/%(rand)s' % {'rand':random.randint(1,10000)}
    else:
        url = 'latex/%(app)s.%(model)s/%(pk)s' % {'app':o.get_app(), 'model':o.get_model(), 'pk':o.pk}
    try:
        text = smart_str(text)
        text.index('<eq>')
        r = cmd.htex2html(text, url, tcolor='4f4f4f')
        return mark_safe(r)
    except:
        return text
    
@register.filter
def has_perm(user, perm=None):
    return funcs.has_perm_simple(user, perm)

@register.filter
def is_same_user(userid, user):
    return funcs.is_same_user(userid, user)

@register.filter
def orfil(a, b):
    return a or b


@register.filter    
def comma(a, b):    
    if isinstance(a, tuple):
        l = list(a)
    else:
        l = [a]
    l.append(b)
    return tuple(l)

@register.filter
def can_add(user, o=None):
    if hasattr(o, "_meta"):
        ct = ContentType.objects.get_for_model(o)
        app = ct.app_label
        model = ct.model
    else:
        t = o.split(".")
        app, model = t[0], t[1] 
    return user.is_authenticated() and user.has_perm('%s.add_%s' % (app, model)) 

@register.filter
def can_change(user, o=None):
    if hasattr(o, "_meta"):
        ct = ContentType.objects.get_for_model(o)
        app = ct.app_label
        model = ct.model
    else:
        t = o.split(".")
        app, model = t[0], t[1] 
    return user.is_authenticated() and (o.get_author == user or user.has_perm('%s.change_%s' % (app,  model))) 

@register.filter
def can_delete(user, o=None):
    if hasattr(o, "_meta"):
        ct = ContentType.objects.get_for_model(o)
        app = ct.app_label
        model = ct.model
    else:
        t = o.split(".")
        app, model = t[0], t[1] 
    return user.is_authenticated() and (o.get_author == user or user.has_perm('%s.delete_%s' % (app, model))) 

@register.filter
def get_ctpk(o):
    try:
        return ContentType.objects.get_for_model(o).id
    except:
        return None
    
@register.filter('get_model')
def get_m(o):
    try:
        return ContentType.objects.get_for_model(o).model
    except:
        return None

def do_escape_pre_code(value):
    soup = BeautifulSoup(value, smartQuotesTo="html")
    for tag in soup.findAll('code'):
        str = u''
        for c in tag.contents:
            str+=unicode(c)
        tag.contents = []
        tag.setString(str.replace('<', '&lt;').replace('>','&gt;'))
        
    return soup.renderContents() 
#        if tag == 'code':
#            print dir(tag) 
            
@register.filter('sanitize')
def sanitize_html(value):
    value = do_escape_pre_code(value)
    valid_tags = 'img IMG p i strong b eq table tr td ul ol li a h1 h2 h3 h4 h5 h6 pre code br sup sub'.split()
    valid_attrs = 'class src href colspan'.split()
    soup = BeautifulSoup(value)
#    for comment in soup.findAll(
#        text=lambda text: isinstance(text, Comment)):
#        comment.extract()
    for tag in soup.findAll(True):
        if tag.name not in valid_tags:
            tag.hidden = True
        tag.attrs = [(attr, val) for attr, val in tag.attrs
                     if attr in valid_attrs]
    return mark_safe(soup.renderContents().decode('utf8').replace('javascript:', ''))

@register.filter("truncate_chars")  
def truncate_chars(value, max_length):  
    if len(value) > max_length:  
        truncd_val = value[:max_length]  
        if value[max_length+1] != " ":  
            truncd_val = truncd_val[:truncd_val.rfind(" ")]  
        return  truncd_val + "..."  
    return value

@register.filter("markupify")  
def auto_transform_markup(text, comment='mrk'):
    """
    Given a comment (``ThreadedComment`` or ``FreeThreadedComment``), this tag
    looks up the markup type of the comment and formats the output accordingly.
    
    It can also output the formatted content to a context variable, if a context name is
    specified.
    """
    if hasattr(comment, 'markup'):
        markup = comment.markup
    else:
        markup = comment
        
    try:
        from django.utils.html import escape
        from django.conf import settings
        if markup == settings.MARKDOWN:
            from django.contrib.markup.templatetags.markup import markdown
            return markdown(text,"extra")
        elif markup == settings.TEXTILE:
            from django.contrib.markup.templatetags.markup import textile
            return textile(text)
        elif markup == settings.REST:
            from django.contrib.markup.templatetags.markup import restructuredtext
            return restructuredtext(text)
        elif markup == settings.HTML:
            return mark_safe(force_unicode(text))
        elif markup == settings.PLAINTEXT:
            return escape(text)
    except ImportError:
        # Not marking safe, in case tag fails and users input malicious code.
        return force_unicode(text)
    
    
@register.filter
def truncatehtml(string, length):
    return text.truncate_html_words(string, length)
truncatehtml.is_safe = True


@register.filter
def yvcut(string, o):
    n = len(string)
#    s = postcut(string, o)
    s = string
    if len(s) == len(string):
        return text.truncate_html_words(string, 256)
    else:
        return s
yvcut.is_safe = True
